package com.hxl.aop.res.aspect;

import com.alibaba.fastjson.JSONObject;
import com.hxl.aop.entity.LiveLog;
import com.hxl.aop.res.AjaxResult;
import com.hxl.aop.res.annotation.Log;
import com.hxl.aop.res.enums.OperatorType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日志切面类
 *
 * @author 霍帅帅
 * @since 2024-08-20 21:30
 */
@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    @Pointcut(value = "@annotation(com.hxl.aop.res.annotation.Log) || @within(com.hxl.aop.res.annotation.Log)")
    public void logPoint() {
    }

    @Around(value = "logPoint()")
    public Object around(ProceedingJoinPoint joinPoint) {
        // 方法执行前后的逻辑
        logger.info("我踏马来啦");
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
            return proceed;
        } catch (Throwable throwable) {
            logger.error("Throwable  {}", (Object) throwable.getStackTrace());
            return AjaxResult.error("出错啦");
        } finally {
            // 继续执行目标方法
            long endTime = System.currentTimeMillis();
            logger.info("Execution time: {} ms", System.currentTimeMillis() - TIME_THREADLOCAL.get());
            logger.info("我踏马走啦");
            handleLog(joinPoint, proceed, getLog(joinPoint));
        }
    }

    @Before(value = "logPoint()")
    public void boBefore(JoinPoint joinPoint) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        logger.info("doAfterReturning");
    }

    @AfterThrowing(throwing = "e", pointcut = "@annotation(controllerLog)")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        logger.info("doAfterThrowing===异常通知");
    }

    protected void handleLog(ProceedingJoinPoint joinPoint, Object proceed, Log log) {
        // 设置方法名称
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        logger.info("className.methodName: {}.{}", className, methodName);
        // 设置请求方式
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestMethod = request.getMethod();
        logger.info("request.getMethod: {}", requestMethod);
        // 处理设置注解上的参数
        String requestURI = request.getRequestURI();
        String remoteAddr = request.getRemoteAddr();

        List<Object> args = Arrays.stream(joinPoint.getArgs()).map(arg -> {
            if (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)) {
                return arg;
            } else {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
        // 设置消耗时间
        logger.info("Execution time: {} ms", System.currentTimeMillis() - TIME_THREADLOCAL.get());
        String title = log.title();
        int operatoraType = log.operatorType().ordinal();
        int businessType = log.businessType().ordinal();
        // 保存数据库
        LiveLog liveLog = new LiveLog(requestURI, requestMethod, TIME_THREADLOCAL.get(),
                getMethodArgumentTypeName((MethodSignature) joinPoint.getSignature()), args, proceed != null ? proceed.toString() : "null",
                (System.currentTimeMillis() - TIME_THREADLOCAL.get()) + "ms", className + "." + methodName,
                remoteAddr, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), ((MethodSignature) joinPoint.getSignature()).getReturnType().getName(), operatoraType, businessType, title);
        logger.info(JSONObject.toJSONString(liveLog));
    }

    private Map<String, String> getMethodArgumentTypeName(MethodSignature method) {
        Map<String, String> map = new HashMap<>();
        String[] argTypeNames = method.getParameterNames();
        Class[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            map.put(parameterTypes[i].getName(), argTypeNames[i]);
        }
        return map;
    }

    public Log getLog(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Log log = AnnotationUtils.findAnnotation(signature.getMethod(), Log.class);
        if (Objects.nonNull(log)) {
            return log;
        }
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), Log.class);
    }

}
